﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Services.Client;
using System.Xml;
using System.IO;
using System.Globalization;

namespace AstoriaPlus
{
	/// <summary>
	/// Provides formatting helpers for dealing with data service client exceptions.
	/// </summary>
	public static class DataServiceExceptionExtensions
	{
		/// <summary>
		/// See <c>System.Data.Services.Client.XmlConstants.DataWebMetadataNamespace</c>
		/// </summary>
		private const string DataWebMetadataNamespace = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata";

		/// <summary>
		/// Formats the current exception message using the given format string. The first replacement 
		/// in the format string ('{0}') is always the current exception message, with the optional 
		/// <paramref name="args"/> added for the other replacements, if any.
		/// </summary>
		public static string FormatMessage(this DataServiceQueryException exception, string format, params object[] args)
		{
			if (exception.InnerException != null)
			{
				string message;
				if (TryGetMessageFromXml(exception.InnerException.Message, out message))
					return FormatMessage(format, message, args);
				else
					return FormatMessage(format, exception.InnerException.Message, args);
			}

			return FormatMessage(format, exception.Message, args);
		}

		/// <summary>
		/// Formats the current exception message using the given format string. The first replacement 
		/// in the format string ('{0}') is always the current exception message, with the optional 
		/// <paramref name="args"/> added for the other replacements, if any.
		/// </summary>
		public static string FormatMessage(this DataServiceRequestException exception, string format, params object[] args)
		{
			if (exception.Response != null)
			{
				var messages = new List<string>();
				foreach (var response in exception.Response.Where(x => x.Error != null))
				{
					string message;
					if (TryGetMessageFromXml(response.Error.Message, out message))
						messages.Add(message);
					else
						messages.Add(response.Error.Message);
				}

				return FormatMessage(format, string.Join(Environment.NewLine, messages), args);
			}

			return FormatMessage(format, exception.Message, args);
		}

		private static string FormatMessage(string format, string message, params object[] args)
		{
			return String.Format(CultureInfo.CurrentCulture, format, new object[] { message }.Concat(args).ToArray());
		}

		private static bool TryGetMessageFromXml(string xmlMessage, out string message)
		{
			message = null;
			try
			{
				using (var xml = XmlReader.Create(new StringReader(xmlMessage)))
				{
					xml.MoveToContent();
					if (xml.ReadToDescendant("message", DataWebMetadataNamespace))
					{
						message = xml.ReadElementContentAsString();

						if (xml.LocalName == "innererror" || xml.ReadToFollowing("innererror", DataWebMetadataNamespace))
						{
							if (xml.ReadToDescendant("message", DataWebMetadataNamespace))
							{
								message += " (" + xml.ReadElementContentAsString() + ")";
							}
						}

						return true;
					}
				}
			}
			catch (XmlException)
			{
			}

			return false;
		}
	}
}
